<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>深拷贝</title>
</head>

<body>
  <script>

    // var obj = {
    //   name: "qqq",
    //   fn: (function (x) {
    //     return x + 10;
    //   })(name)
    // };
    // console.log(obj.fn);

    // 手写深拷贝
    // 1、实现一个功能类似JSON.parse(JSON.stringify())的简单深拷贝
    //判读是否为对象

    // function isObject(target) {
    //   const type = typeof target;
    //   return target !== null
    //     && (type === 'object' || type === 'function');
    // }

    // // 深拷贝函数
    // function deepClone(target, cache = new WeakSet) {
    //   if (!isObject(target)) {
    //     return target;
    //   }
    //   // 增加缓存机制
    //   if (cache.has(target)) {
    //     return target;
    //   }
    //   cache.add(target);

    //   let cloneTarget = Array.isArray(target) ? [] : {}; // 判断拷贝的是否是数组
    //   Reflect.keys(target).forEach(key => {
    //     cloneTarget[key] = deepClone(target[key]); // 递归进行拷贝
    //   });
    //   return cloneTarget;
    // }

    // let obj = { name: '烟花渲染离别', hobby: ['看动漫'] };
    // let obj2 = deepClone(obj);
    // obj2.name = '七宝';
    // console.log(obj.name); // 烟花渲染离别
    // console.log(obj2.name); // 七宝
    // obj.hobby.push('打球');
    // console.log(obj.hobby); // ['看动漫', '打球']
    // console.log(obj2.hobby); // ['看动漫']

    // ***使用拷贝对象的构造方法创建对应类型的数据***
    // 我们需要先知道要知道要拷贝的是什么类型的对象，
    // 使用Object.prototype.toString.call()来获取对象的准确类型。
    const arrayTag = '[object Array]'
    const objectTag = '[object Object]'
    const mapTag = '[object Map]'
    const setTag = '[object Set]'
    const regexpTag = '[object RegExp]'
    const boolTag = '[object Boolean]'
    const numberTag = '[object Number]'
    const stringTag = '[object String]'
    const symbolTag = '[object Symbol]'
    const dateTag = '[object Date]'
    const errorTag = '[object Error]'
    const functionTag = '[object Function]'
    // 初始化Array
    function cloneArray(array) {
      const { length } = array;
      const result = new array.constructor(length);
      if (length && typeof array[0] === 'string'
        && hasOwnProperty.call(array, 'index')) {
        result.index = array.index;
        result.input = array.input;
      }
      return result;
    }

    // 初始化Symbol
    function cloneSymbol(symbol) {
      return Object(Symbol.prototype.valueOf.call(symbol));
    }

    // 初始化正则RegExp
    function cloneRegExp(regexp) {
      const reFlag = /\w*$/;  // \w 用于匹配字母，数字或下划线字符，相当于[A-Za-z0-9_
      const result = new regexp.constructor(regexp.source, reFlag.exec(regexp)) // 返回当前匹配的文本
      result.lastIndex = regexp.lastIndex; // 下一次匹配的起始索引
      return result;
    }

    // 创建拷贝对象,我们可以根据对应的类型进行初始化对象的操作。
    // 通过target.constructor拿到拷贝对象的构造函数，通过源对象的构造函数生成的对象可以保留对象原型上的数据
    // 1、Boolean、Number、String、Date、Error我们可以直接通过构造函数和原始数据创建一个新的对象。
    // 2、Object、Map、Set我们直接执行构造函数返回初始值，递归处理后续属性，因为它们的属性可以保存对象。
    // 3、Array、Symbol、RegExp进行特殊处理。
    function initCloneTargetByTag(target, tag) {
      const tagConstructor = target.constructor;
      switch (tag) {
        case boolTag:
        case dateTag:
          return new tagConstructor(+target);

        case numberTag:
        case stringTag:
        case errorTag:
          return new tagConstructor(target);
        case objectTag:
        case mapTag:
        case setTag:
          return new tagConstructor();
        case arrayTag:
          return cloneArray(target);
        case symbolTag:
          return cloneSymbol(target);
        case regexpTag:
          return cloneRegExp(target);
      }
    }

    // 判读是否为对象
    function isObject(target) {
      const type = typeof target;
      return target !== null
        && (type === 'object' || type === 'function');
    }

    // 深拷贝函数
    function deepClone(target, cache = new WeakSet) {
      if (!isObject(target)) return target; // 拷贝基本类型
      // 增加缓存机制
      if (cache.has(target)) return target;
      cache.add(target);
      // 可以使用Object.prototype.toString.call()来获取对象的准确类型。
      const tag = Object.prototype.toString.call(target);
      // 使用拷贝对象的构造方法创建对应类型的数据
      let cloneTarget = initCloneTargetByTag(target, tag);

      if (tag === mapTag) {
        target.forEach((value, key) => {
          cloneTarget.set(key, deepClone(value, tag));
        });
        return cloneTarget;
      }
      if (tag === setTag) {
        target.forEach(value => {
          cloneTarget.add(deepClone(value, map));
        });
        return cloneTarget;
      }
      if (tag === functionTag) {
        return target;
      }
      Reflect.ownKeys(target).forEach(key => {
        cloneTarget[key] = deepClone(target[key]); // 递归进行拷贝
      });
      return cloneTarget;
    }

    // 简单版
    function deepMerge(obj) {
      //类型校验省略
      var target = {};
      for (var key in obj) {
        let itemObj = obj[key];
        if (typeof itemObj == 'object') {
          target[key] = deepMerge(itemObj)
        } else {
          target[key] = obj[key]
        }
      }
      return target;
    }

    const map = new Map();
    map.set('烟花', '渲染离别');
    map.set('掘金', 'https://juejin.cn/user/2101921961223614');

    const set = new Set();
    set.add('set1');
    set.add('set2');

    let target = {
      arr: [1, 2, 3],
      bool: false,
      bool2: new Boolean(true),
      date: new Date(),
      empty: null,
      error: new Error(),
      func: () => {
        console.log('我是函数');
      },
      map,
      num: 1,
      num2: new Number(1),
      obj: {
        children: {
          name: '我是子对象'
        }
      },
      reg: /\w*$/,
      set,
      str: '烟花渲染离别',
      str2: new String('new String'),
      symbol: Symbol('symbol'),
      symbol: Object(Symbol('new Symbol')),
      undefined: undefined,
    };

    let cloneTarget = deepClone(target);
    console.log(cloneTarget);
    target.obj.children.name = '修改子对象1';
    console.log(cloneTarget.obj.children.name);

    let cloneTarget1 = deepMerge(target);
    console.log(cloneTarget1);
    target.obj.children.name = '修改子对象2';
    console.log(cloneTarget1.obj.children.name);

  </script>
</body>

</html>